Skip to content

Optimize API calls and enhance concurrency in TraktRepository - #122

Merged
ProdigyV21 merged 12 commits into
ProdigyV21:mainfrom
Himanth-reddy:Performance-improvements
Apr 5, 2026
Merged

Optimize API calls and enhance concurrency in TraktRepository#122
ProdigyV21 merged 12 commits into
ProdigyV21:mainfrom
Himanth-reddy:Performance-improvements

Conversation

@Himanth-reddy

Copy link
Copy Markdown
Collaborator

This pull request introduces substantial improvements to concurrency handling and data enrichment in the IPTV and Trakt repositories. The most significant changes include refactoring blocking thread pool operations to use coroutines for parallel network requests, optimizing TMDB season data enrichment with a coroutine-safe cache, and enhancing error handling and progress tracking during EPG data fetches. Additionally, new data models were added to support richer TV details.

Concurrency and Parallelism Improvements

  • Refactored all major blocking thread pool operations in IptvRepository to use coroutines with limited parallelism, improving efficiency and scalability for fetching EPG and channel data. This includes new coroutine-based implementations for fetching Xtream EPG listings and short EPG data, replacing manual thread pool management. [1] [2] [3] [4] [5]
  • Added a benchmark (IptvBenchmark.kt) to compare thread pool and coroutine performance for parallel tasks.

Network and API Handling Enhancements

  • Converted the requestJson method in IptvRepository to a suspend function using OkHttp's async API, making network requests cancellable and coroutine-friendly.

EPG Fetching and Progress Reporting

  • Improved error handling and progress updates during EPG fetching, including early termination if too many errors occur and more accurate progress reporting.

Trakt Repository Data Enrichment

  • Implemented a coroutine-safe cache for TMDB season details in TraktRepository, preventing duplicate network requests when enriching "continue watching" items and ensuring efficient concurrent access. [1] [2] [3]
  • Updated the deletion of stale playback records in TraktSyncService to run in parallel with limited concurrency, improving sync speed and reliability. [1] [2]

Data Model Extensions

  • Added a TmdbTvSeason data class and included a seasons field in TmdbTvDetails to support richer TV metadata. [1] [2]

These changes modernize the codebase's approach to concurrency, improve network efficiency, and lay the groundwork for richer media metadata support.

Himanth-reddy and others added 11 commits April 5, 2026 14:40
💡 What:
Implemented an ephemeral cache (ConcurrentHashMap) during the parallel Continue Watching enrichment loops in TraktRepository.kt. The cache ensures that if multiple episodes of the same season are queried simultaneously, only one API call goes out and the other queries await the result.

🎯 Why:
Previously, enrichContinueWatchingItems fetched season information via tmdbApi.getTvSeason() for every Continue Watching item synchronously inside its coroutine. In scenarios where a user is re-watching or has multiple items from the same season of a show, it fired identical network calls concurrently leading to unnecessary network/CPU utilization.

📊 Measured Improvement:
Due to difficulties instantiating TraktRepository without Roboelectric or extensive mocks of DataStore, I have skipped creating a formal Android benchmark test suite instance to measure this precisely in CI. However, logically, a user with N items of the same season went from O(N) API calls down to O(1), removing network roundtrip overhead entirely for N-1 items during parallel awaitAll() execution.
💡 What:
Implemented an ephemeral cache using `putIfAbsent` and `CompletableDeferred` during the parallel Continue Watching enrichment loops in TraktRepository.kt. The cache ensures that if multiple episodes of the same season are queried simultaneously, only one API call goes out and the other queries await the result via `putIfAbsent`.

🎯 Why:
Previously, enrichContinueWatchingItems fetched season information via tmdbApi.getTvSeason() for every Continue Watching item synchronously inside its coroutine. In scenarios where a user is re-watching or has multiple items from the same season of a show, it fired identical network calls concurrently leading to unnecessary network/CPU utilization.

📊 Measured Improvement:
Due to difficulties instantiating TraktRepository without Roboelectric or extensive mocks of DataStore, I have left the benchmark test as a documented placeholder. However, logically, a user with N items of the same season went from O(N) API calls down to O(1), removing network roundtrip overhead entirely for N-1 items during parallel awaitAll() execution.
- Replaced multiple `getTvSeason` API calls with a single `getTvDetails` call
- Updated API models `TmdbTvDetails` and `TmdbTvSeason`
- Abstracted caching logic to `ensureSeasonEpisodeCountsCached`
…s and add per-show in-flight guard to avoid concurrent API calls
What: Replaced the sequential forEach loop in cleanupTraktPlaybackProgress with a parallel execution pattern using coroutineScope, async, and awaitAll. Wrapped the network requests with a Semaphore(5) to bound concurrency to 5 parallel requests.

Why: The previous implementation was suffering from an N+1 Database Operation loop. For each stale record, it performed a sequential network request (supabaseApi.deleteWatchHistory) to Supabase. This resulted in significant I/O delays proportional to the number of stale records.

Measured Improvement: Simulated a 75ms network latency for the Supabase network call across 50 stale playback records in a benchmark test.
Baseline (Sequential time): ~3766ms.
Improved (Concurrent time with Semaphore 5): ~754ms.
Speedup: ~5.0x
- Add supabaseAuthMutex to serialize auth repository refresh calls in executeSupabaseCall to prevent parallel DataStore writes and conflicting requests.
- Rethrow CancellationException when cleaning up stale playbacks so the concurrent mapped tasks observe regular coroutine semantics.
…331`

Docstrings generation was requested by @Himanth-reddy.

The following files were modified:

* `IptvBenchmark.kt`
* `app/src/main/kotlin/com/arflix/tv/data/repository/IptvRepository.kt`

These files were ignored:
* `app/src/test/kotlin/com/arflix/tv/data/repository/IptvBenchmarkTest.kt`

These file types are not supported:
* `update_iptv_repo.patch`
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@ProdigyV21

Copy link
Copy Markdown
Owner

The concurrency work in this PR is genuinely valuable — moving IptvRepository off manual thread pools onto coroutine-based parallelism, the coroutine-safe TMDB season cache in TraktRepository, parallel stale-record cleanup in TraktSyncService, and the new TmdbTvSeason model are all good improvements. CI is green and the diff is mergeable.

However, this PR cannot be merged as-is because it also adds a large number of developer scratchpad / AI-assistant artifacts at the repo root that should not ship with the app. Please remove the following before merge:

One-off patch scripts (all at repo root):

  • fix_brace.py
  • fix_brace2.py
  • fix_calc.py
  • fix_callers.py
  • fix_concurrency.py
  • fix_tier2.py
  • patch_atomic.py
  • patch_auth.py
  • patch_benchmark.py
  • patch_cancellation.py
  • patch_comment.py
  • patch_distinct.py
  • patch_iptv.py
  • patch_mutex.py
  • patch_requestjson.py
  • patch_requestjson2.py
  • patch_requestjson3.py
  • patch_timeout.py
  • update_iptv_repo.patch

Misplaced Kotlin file:

  • IptvBenchmark.kt at the repo root — this is not under app/src/..., so it isn't part of the module source set and shouldn't be committed. The actual benchmark test already lives at app/src/test/kotlin/com/arflix/tv/data/repository/IptvBenchmarkTest.kt, which is the correct location.

Quick cleanup:

\\�ash
git rm fix_.py patch_.py update_iptv_repo.patch IptvBenchmark.kt
git commit --amend --no-edit # or a new commit
git push --force-with-lease
\\

After that cleanup the remaining diff (~6 production files + 2 tests) is the real change and is worth a careful re-review before merge, since IptvRepository is core to playback. Specifically I'd like to verify:

  1. The suspend conversion of requestJson and the coroutine cancellation semantics don't introduce new cases where a cancelled caller leaves an OkHttp call in flight.
  2. The new limited-parallelism dispatcher bounds are sane for the typical EPG fetch volume.
  3. The new TraktRepository TMDB-season cache has a bounded lifetime or invalidation path (to avoid stale season metadata after content updates).

Happy to give it a full review once the scratchpad files are gone.

@ProdigyV21

Copy link
Copy Markdown
Owner

Thanks for addressing every concern from the earlier review. Re-audited the final diff against current main (which now includes the 14 bug/feature PRs that merged this morning) and everything checks out:

Blockers resolved:

  • All 19 scratchpad fix_*.py / patch_*.py / update_iptv_repo.patch files removed in 21f02d4.
  • Root-level IptvBenchmark.kt removed (the real benchmark test lives at app/src/test/kotlin/.../IptvBenchmarkTest.kt as expected).
  • Final diff is exactly the 7-file production + test change I said would be merge-worthy after cleanup.

Three original concerns verified:

  1. requestJson suspend conversion cancellation semantics — textbook correct. continuation.invokeOnCancellation { call.cancel() } cancels the in-flight OkHttp call, if (continuation.isActive) guards prevent double-resume, response.close() handles the arrived-after-cancellation case. No in-flight leaks.

  2. Limited-parallelism dispatcher boundsDispatchers.IO.limitedParallelism(20) with 60s withTimeoutOrNull is a direct 1-for-1 replacement for the old newFixedThreadPool(20) + 60s awaitTermination. The small-favorites path went from min(10, size) to a fixed 20, but that's still bounded and safe.

  3. TMDB-season cache lifetime — my concern assumed a long-lived @Singleton field; the author correctly chose an ephemeral ConcurrentHashMap<Pair<Int,Int>, Deferred<TmdbSeasonDetails?>> that's a local variable of each batch enrichment call. Allocated per enrichContinueWatchingItems invocation, populates during awaitAll, garbage-collected when the batch finishes. No stale-data possible because each new home load creates a fresh cache. The putIfAbsent + CompletableDeferred pattern is the standard atomic single-flight implementation.

Additional fixes from the review-comment iterations that I also verified:

  • supabaseAuthMutex.withLock { authRepository.refreshAccessToken() } in TraktSyncService — serializes concurrent token refreshes and prevents conflicting DataStore writes. Genuine bug fix, not just a theoretical concern.
  • if (e is CancellationException) throw e in the parallel stale-playback cleanup — correctly preserves structured concurrency semantics.
  • inFlightRequests guard in AnimeMapper.ensureSeasonEpisodeCountsCached — per-show single-flight dedupe with explicit cleanup in both success and exception paths (inFlightRequests.remove(tmdbId)), so the map doesn't grow unbounded.
  • Nullability fix in calculateTmdbSeasonOffset return type (IntInt?) — callers now correctly distinguish "couldn't determine offset" from "offset is 0".

Minor nits (not blockers, not addressing them):

  • IptvBenchmarkTest uses Thread.sleep() inside async { ... } instead of delay(), so it's actually measuring thread blocking rather than coroutine semantics. The benchmark numbers it prints aren't a fair comparison, but since the test only prints to stdout and asserts nothing, it can't regress or flake anything.
  • TraktRepositoryBenchmarkTest asserts 1 == 1 as a placeholder — the author's comment honestly notes that properly mocking TraktRepository requires Robolectric or MockK which aren't set up in this project. Zero test value but zero harm.
  • Pre-existing catch (e: Exception) in AnimeMapper that swallows CancellationException without rethrow — inherited from original code, not introduced by this PR, so not a regression. Worth fixing in a follow-up.

Mergeability: CLEAN. Dry-run merge against current main confirms no conflicts. My 14 merged PRs' added files (ApkInstallReceiver.kt, JikanApi.kt, MobileBackButton.kt, etc.) all remain untouched because the PR branch doesn't reference them.

Squash-merging now. Thanks for the thorough follow-through — this is a genuinely valuable concurrency cleanup that lays the groundwork for richer TV metadata (the new TmdbTvSeason/seasons field).

@ProdigyV21
ProdigyV21 merged commit 60ba158 into ProdigyV21:main Apr 5, 2026
2 checks passed
@Himanth-reddy
Himanth-reddy deleted the Performance-improvements branch April 12, 2026 17:17
@Himanth-reddy Himanth-reddy added gssoc-approved level:advanced Advanced level task quality:exceptional Exceptional code implementation type:performance Performance optimization type:refactor Code refactoring and cleanups gssoc:approved GSSoC approved contribution quality:clean Clean code implementation and removed gssoc:approved quality:exceptional Exceptional code implementation labels Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved GSSoC approved contribution level:advanced Advanced level task quality:clean Clean code implementation type:performance Performance optimization type:refactor Code refactoring and cleanups

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants